/* SPDX-License-Identifier: GPL-2.0+
 *
 * Copyright (c) 2016-2018, LomboTech Co.Ltd. All rights reserved.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/module.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/dmaengine_pcm.h>
#include <sound/soc.h>

#include "lombo_dmaengine.h"

#define DMA_BUFFER_MAX		(1024*64)
#define PERIOD_BYTES_MIN	32
#define PERIOD_BYTES_MAX	(PAGE_SIZE*4)
#define PERIOD_MIN		1
#define PERIOD_MAX		64
#define PREALLOC_PCM_BUFFER	(1024*64)

/* define pcm dma params */
static const struct snd_pcm_hardware lombo_pcm_hardware = {
	.info			= SNDRV_PCM_INFO_INTERLEAVED |
					SNDRV_PCM_INFO_BLOCK_TRANSFER |
					SNDRV_PCM_INFO_MMAP |
					SNDRV_PCM_INFO_MMAP_VALID,
	/* max dma buffer bytes */
	.buffer_bytes_max	= DMA_BUFFER_MAX,
	/* min bytes for one period */
	.period_bytes_min	= PERIOD_BYTES_MIN,
	/* max bytes for one period */
	.period_bytes_max	= PERIOD_BYTES_MAX,
	.periods_min		= PERIOD_MIN,		/* min periods */
	.periods_max		= PERIOD_MAX,		/* max periods */
};

static int dma_slave_config(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params, struct dma_slave_config *slave_config)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_dmaengine_dai_dma_data *dma_data;
	int ret;
	dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
	ret = snd_hwparams_to_dma_slave_config(substream, params, slave_config);
	if (ret)
		return ret;
	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		slave_config->dst_addr = dma_data->addr;
		slave_config->dst_maxburst = dma_data->maxburst;
		slave_config->src_maxburst = dma_data->maxburst;
	} else {
		slave_config->src_addr = dma_data->addr;
		slave_config->src_maxburst = dma_data->maxburst;
		slave_config->dst_maxburst = dma_data->maxburst;
	}
	if (dma_data->addr_width != DMA_SLAVE_BUSWIDTH_UNDEFINED) {
		slave_config->dst_addr_width = dma_data->addr_width;
		slave_config->src_addr_width = dma_data->addr_width;
	}
	slave_config->slave_id = dma_data->slave_id;
	return 0;
}

static int do_fmt_process(struct snd_pcm_substream *substream,
			   int channel, unsigned long hwoff,
			   void *buf, unsigned long bytes)
{
	u32 *data_ptr = NULL;
	void *dma_ptr = NULL;
	int len = 0;
	int i = 0;
	struct snd_pcm_runtime *runtime = substream->runtime;

	if (runtime->format != SNDRV_PCM_FORMAT_S24_LE)
		return 0;

	dma_ptr = runtime->dma_area + hwoff +
		channel * (runtime->dma_bytes / runtime->channels);

	data_ptr = (u32 *)dma_ptr;
	len = bytes >> 2;
	for (i = 0; i < len; i++)
		data_ptr[i] <<= 8;

	return 0;
}

static const struct snd_dmaengine_pcm_config lombo_dmaengine_pcm_config = {
	.prepare_slave_config	= dma_slave_config,
	.prealloc_buffer_size	= PREALLOC_PCM_BUFFER,
	.pcm_hardware	= &lombo_pcm_hardware,
	.process	= do_fmt_process,
};

int lombo_dmaengine_pcm_register(struct device *dev)
{
#ifdef CONFIG_ARCH_LOMBO_N7V3
	return devm_n7v3_dmaengine_pcm_register(dev,
		&lombo_dmaengine_pcm_config,
		SND_DMAENGINE_PCM_FLAG_COMPAT);
#else
	return devm_snd_dmaengine_pcm_register(dev, &lombo_dmaengine_pcm_config,
		SND_DMAENGINE_PCM_FLAG_COMPAT);
#endif
}
EXPORT_SYMBOL_GPL(lombo_dmaengine_pcm_register);

MODULE_LICENSE("GPL");

